#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <errno.h>

#include "ddns_proto.h"
#include "debug.h"

#define CMD_POS 3
#define DATA_POS 6

const static sproto_header ddns_header = 
{
	.type = DDNS_PROTO_TYPE,
	.name = DDNS_PROTO_NAME,
	.version = DDNS_PROTO_VER,
	.length = sizeof(sddns_content),
	.build = ddns_build_package,
	.parse = ddns_parse_package
};
const sproto_header *ddns_default_header = &ddns_header;

static unsigned char cmd_maps[][2] = 
{// {send-cmd,expected-response-cmd}
// 0xa1 -- device ack, 0x05 -- server ack
	{0x01,0xa1},
	{0x02,0xa3},
	{0x03,0xa1},
	{0x04,0xa1},
	{0x05,0x00},
	{0x06,0xa2},
	{0x08,0xa7},
	{0x09,0xa1},
	{0xa4,0x05},
	{0xa5,0x05},
	{0xa6,0x07},
	{0xa8,0x05}
};

static unsigned char response_cmd(unsigned char cmd)
{
	int size = sizeof(cmd_maps)/2;
	int res_cmd = 0;
	int i;

	for(i=0;i<size;i++)
	{
		if(cmd == cmd_maps[i][0])
		{
			res_cmd = cmd_maps[i][1];
			break;
		}
	}

	return res_cmd;
}

#if 0
static int analyse_serv_ip_port(RECV_DATA_S *pst_recv_data, SERV_INFO_S *pst_serv_info)
{
    unsigned char c = ';';
    unsigned char port[6] = {0};
    short i = 0, founded = 0;
    RECV_DATA_S *st_recv_data = pst_recv_data;
    SERV_INFO_S *st_serv_info = pst_serv_info;

    while(i < st_recv_data->pkg_size)
    {
        if(st_recv_data->buff[i] == c)
        {
            founded = 1;
            break;
        }

        i++;
    }

    if(founded)
    {
        PLOG(DBG, "found char %c and the position is %d\n", c, i);
    }
    else
    {
        PLOG(DBG, "can not find char '%c'\n", c);
        return -3;
    }

    memset(st_serv_info->serv_ip_2, 0, INET_ADDRSTRLEN);
    memcpy(st_serv_info->serv_ip_2, &st_recv_data->buff[6], i - 6);
    memcpy(port, &st_recv_data->buff[i + 1], st_recv_data->pkg_size - (i + 1) - 2);
    //st_serv_info->serv_port = atoi(port);
    st_serv_info->serv_port1 = atoi(port);
    PLOG(DBG, "get server ip:%s\n", st_serv_info->serv_ip_2);
    //PLOG(DBG, "get server port:%d\n", st_serv_info->serv_port);
    PLOG(DBG, "get server port:%d\n", st_serv_info->serv_port2);
    return 0;
}
#endif

static int build_up_package(unsigned int cmd,uddns_up_content *content,unsigned char *buff,int *len)
{
	switch(cmd)
	{
		case CLI_RESP:
			buff[0] = content->resp_result&0xff;
			*len = 1;
			break;

		case CLI_UP_PW:
			strcpy((char*)buff,content->login_pwd);
			*len = strlen((char*)buff)+1;
			break;

		case CLI_UP_IP:
			*len = snprintf((char*)buff,*len,"%s;%s;%u;%u",content->up_ipinfo.id,
					content->up_ipinfo.ip,content->up_ipinfo.web_port,
					content->up_ipinfo.stream_port);
			*len += 1;
			break;

		case CLI_UP_LOGIN:
			*len = snprintf((char*)buff,*len,"%s;%s;%u;%u;%s;%s",content->up_login.id,
					content->up_login.ip,content->up_login.web_port,
					content->up_login.stream_port,content->up_login.type,
					content->up_login.version);
			*len += 1;
			break;

		case CLI_UP_HEART:
			*len = snprintf((char*)buff,*len,"%s;%u;%u",content->up_heart.id,
					content->up_heart.web_port,content->up_heart.stream_port);
			*len += 1;
			break;

		case CLI_UP_CHECKUPNP:
			*len = snprintf((char*)buff,*len,"%s;%u;%u",content->up_checkupnp.id,
					content->up_checkupnp.web_port,content->up_checkupnp.stream_port);
			*len += 1;
			break;
		
		case CLI_UP_ASKDOMAIN:
			buff[0] = content->up_askdomain&0xff;
			*len = 1;
			break;

		case CLI_DEV_DBG:
			*len = snprintf((char*)buff,*len,"%s",content->dbg_devid);
			*len += 1;
			break;
		default:
			break;
	}

	return 0;
}

static int build_down_package(unsigned int cmd,uddns_down_content *content,unsigned char *buff,int *len)
{
	switch(cmd)
	{
		case SERV_REQ_TIME:
			buff[0] = (content->req_time>>8)&0xff;
			buff[1] = content->req_time&0xff;
			*len = 2;
			break;

		case SERV_GET_IP:
		case SERV_REBOOT_DEV:
		case SERV_GET_PW:
			*len = 0;
			break;

		case SERV_SET_IP_PORT:
			*len = snprintf((char*)buff,*len,"%s;%u",content->set_ip_port.ip,content->set_ip_port.port);
			*len += 1;
			break;

		case SERV_RESP:
			buff[0] = content->resp_result&0xff;
			*len = 1;
			break;

		case SERV_ASK_UPNP:
			buff[0] = content->upnp_result&0xff;
			*len = 1;
			break;

		case SERV_SET_DOMAIN:
			*len = snprintf((char*)buff,*len,"%s",content->domain);
			*len += 1;
			break;

		case SERV_START_DBG:
			buff[0] = content->debug_flag&0xff;
			*len = 1;
			break;

		default:
			break;
	}

	return 0;
}

int ddns_build_package(eproto_role role,sproto_msg *pmsg, unsigned char *sendbuff,int *len)
{
	int ret = 0;
	unsigned char buf_tmp[512] = {0};
	int tmplen = 512;
	unsigned char *tmpbuf = &buf_tmp[0];
	unsigned int cmd = pmsg->cmd;
	sddns_content *content =(sddns_content *)pmsg->content;

	if(content == NULL || sendbuff == NULL || len == NULL)
	{
		PLOG(ERR,"invalid params!\n");
		return VPI_EPARAMS;
	}
	if(*len >= tmplen)
	{
		tmpbuf = sendbuff;
		tmplen = *len;
	}
	if(IS_DNS_UPCMD(cmd)&&role==ROLE_DEV)
	{
		tmpbuf[0] = CLI_TOP_1;
		tmpbuf[1] = CLI_TOP_2;
		tmpbuf[2] = content->seq;
		tmpbuf[3] = cmd&0xff;
		tmpbuf[4] = tmpbuf[5] = 0xff;
		tmplen -= 8;// 6+checksum+end
		// build param content and return param length use tmplen
		ret = build_up_package(cmd,&content->up_content,tmpbuf+6,&tmplen);
		if(ret == 0)
		{
			/* little endian */
			//tmpbuf[4] = tmplen&0xff;
			//tmpbuf[5] = (tmplen>>8)&0xff;
			/* big endian */
			tmpbuf[4] = (tmplen>>8)&0xff;
			tmpbuf[5] = tmplen&0xff;
			tmpbuf[6+tmplen] = vpi_calc_checksum(tmpbuf,tmplen+6);
			tmpbuf[7+tmplen] = CLI_END;
			tmplen += 8;
		}
	}
	else if(IS_DNS_DOWNCMD(cmd)&&role==ROLE_SERVER)
	{
		tmpbuf[0] = SERV_TOP_1;
		tmpbuf[1] = SERV_TOP_2;
		tmpbuf[2] = content->seq;
		tmpbuf[3] = cmd;
		tmpbuf[4] = tmpbuf[5] = 0xff;
		tmplen -= 8;
		ret = build_down_package(cmd,&content->down_content,tmpbuf+6,&tmplen);
		if(ret == 0)
		{
			/* little endian */
			//tmpbuf[4] = tmplen&0xff;
			//tmpbuf[5] = (tmplen>>8)&0xff;
			/* big endian */
			tmpbuf[4] = (tmplen>>8)&0xff;
			tmpbuf[5] = tmplen&0xff;
			tmpbuf[6+tmplen] = vpi_calc_checksum(tmpbuf,tmplen+6);
			tmpbuf[7+tmplen] = SERV_END;
			tmplen += 8;
		}
	}
	else
	{
		PLOG(ERR,"cmd 0x%x and role %d is unsupported!\n",cmd,role);
		return VPI_EFAIL;
	}

	if(!ret && *len >= tmplen)
	{// buff is enough big
		PLOG(DBG,"send buff len = %d.\n", tmplen);
		if(tmpbuf != sendbuff)
		{
			memset(sendbuff,0,*len);
			memcpy(sendbuff,tmpbuf,tmplen);
		}
		*len = tmplen;
		vpi_print_package(sendbuff,*len);
	}
	else if(!ret && *len < tmplen)
	{
		PLOG(ERR,"send buff is not enough,%d < %d[need]\n",*len,tmplen);
		ret = VPI_ESPACE;
	}

	return ret;
}

static int parse_up_package(unsigned int cmd,uddns_up_content *content,unsigned char *buff,int len)
{
	int num = 0;
	int ret = 0;
	unsigned char ck_sum = buff[len];///!preserve checksum to restore it  

	memset(content,0,sizeof(uddns_up_content));
	switch(cmd)
	{
		case CLI_RESP:
			content->resp_result = buff[0];
			break;

		case CLI_UP_PW:
			strncpy(content->login_pwd,(char*)buff,len);
			break;

		case CLI_UP_IP:
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf((char*)buff,"%[^;];%[^;];%u;%u",content->up_ipinfo.id,
					content->up_ipinfo.ip,&content->up_ipinfo.web_port,
					&content->up_ipinfo.stream_port);
			buff[len] = ck_sum;/// restore checksum
			if(num < 4)
			{
				PLOG(ERR,"CLI_UP_IP cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;

		case CLI_UP_LOGIN:
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf((char*)buff,"%[^;];%[^;];%u;%u;%[^;];%s",content->up_login.id,
					content->up_login.ip,&content->up_login.web_port,
					&content->up_login.stream_port,content->up_login.type,
					content->up_login.version);
			buff[len] = ck_sum;/// restore checksum
			if(num < 6)
			{
				PLOG(ERR,"CLI_UP_LOGIN cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;

		case CLI_UP_HEART:
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf((char*)buff,"%[^;];%u;%u",content->up_heart.id,
					&content->up_heart.web_port,&content->up_heart.stream_port);
			buff[len] = ck_sum;/// restore checksum
			if(num < 3)
			{
				PLOG(ERR,"CLI_UP_HEART cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;

		case CLI_UP_CHECKUPNP:
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf((char*)buff,"%[^;];%u;%u",content->up_checkupnp.id,
					&content->up_checkupnp.web_port,&content->up_checkupnp.stream_port);
			buff[len] = ck_sum;/// restore checksum
			if(num < 3)
			{
				PLOG(ERR,"CLI_UP_CHECKUPNP cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;
		
		case CLI_UP_ASKDOMAIN:
			content->up_askdomain = buff[0];
			break;

		case CLI_DEV_DBG:
			memcpy(content->dbg_devid,buff,len);
			break;
		default:
			break;
	}

	return ret;
}

static int parse_down_package(unsigned int cmd,uddns_down_content *content,unsigned char *buff,int len)
{
	int num = 0,ret = 0;
	unsigned char ck_sum = buff[len];///!preserve checksum to restore it  

	memset(content,0,sizeof(uddns_down_content));
	switch(cmd)
	{
		case SERV_REQ_TIME:
			//content->req_time = (buff[0]<<8) + buff[1];
			content->req_time = buff[0];// max value: 0x003C(60s)
			break;

		case SERV_GET_IP:
		case SERV_REBOOT_DEV:
		case SERV_GET_PW:
			break;

		case SERV_SET_IP_PORT:
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf((char*)buff,"%[^;];%u",content->set_ip_port.ip,&content->set_ip_port.port);
			buff[len] = ck_sum;/// restore checksum
			if(num < 2)
			{
				PLOG(ERR,"SERV_SET_IP_PORT cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;

		case SERV_RESP:
			content->resp_result = buff[0];
			break;

		case SERV_ASK_UPNP:
			content->upnp_result = buff[0];
			break;

		case SERV_SET_DOMAIN:
			memcpy(content->domain,buff,len);
			break;

		case SERV_START_DBG:
			content->debug_flag = buff[0];
			break;

		default:
			break;
	}

	return ret;
}

int ddns_parse_package(eproto_role role,sproto_msg *pmsg, unsigned char *recvbuff, int len)
{
	unsigned int *cmd = &pmsg->cmd;
	sddns_content *content = (sddns_content*)pmsg->content;
	int data_len = 0;
	int ret = 0;
	int expected_res_cmd = 0;

	if(content == NULL || recvbuff == NULL || len <= 0)
	{
		PLOG(ERR, "invalid params!\n");
		return VPI_EPARAMS;
	}
	
	vpi_print_package(recvbuff,len);
	if(role==ROLE_DEV)
	{
		if(recvbuff[0]!=SERV_TOP_1 || recvbuff[1]!=SERV_TOP_2 || 
			recvbuff[len-1]!=SERV_END) // || recvbuff[2]!=content->seq
		{
			PLOG(ERR,"recv package is invalid in device role.\n");
			ret = VPI_EFORMAT;
		}
		else if(recvbuff[len-2]!=vpi_calc_checksum(recvbuff,len-2))
		{
			PLOG(ERR,"recv package checksum error!\n");
			ret = VPI_ECHECKSUM;
		}
		else if(IS_DNS_UPCMD(*cmd) && 
				(expected_res_cmd=response_cmd(*cmd))!=0 &&
				recvbuff[3]!=expected_res_cmd)
		{
			PLOG(ERR,"recv cmd 0x%02x is not respond to send cmd 0x%02x\n",recvbuff[CMD_POS],*cmd);
			ret = VPI_ERESPCMD;
		}
		else
		{
			PLOG(DBG,"recv ok,parse the content...\n");
			*cmd = recvbuff[3];
			/* little endian */
			// data_len = recvbuff[4] + (recvbuff[5]<<8);
			/* big endian */
			data_len = recvbuff[5] + (recvbuff[4]<<8);
			if(data_len > 0) // have params
				ret = parse_down_package(*cmd,&content->down_content,recvbuff+6,data_len);
		}
	}
	else if(role==ROLE_SERVER)
	{
		if(recvbuff[0]!=CLI_TOP_1 || recvbuff[1]!=CLI_TOP_2 ||
			recvbuff[len-1]!=CLI_END)// || recvbuff[2]!=content->seq
		{
			PLOG(ERR,"recv package is invalid in server role.\n");
			ret = VPI_EFORMAT;
		}
		else if(recvbuff[len-2]!=vpi_calc_checksum(recvbuff,len-2))
		{
			PLOG(ERR,"recv package checksum error!\n");
			ret = VPI_ECHECKSUM;
		}
		else if(IS_DNS_DOWNCMD(*cmd) && 
				(expected_res_cmd=response_cmd(*cmd))!=0 &&
				recvbuff[3]!=expected_res_cmd)
		{
			PLOG(ERR,"recv cmd 0x%02x is not respond to send cmd 0x%02x\n",recvbuff[CMD_POS],*cmd);
			ret = VPI_ERESPCMD;
		}
		else
		{
			PLOG(DBG,"recv ok,parse the content...\n");
			*cmd = recvbuff[3];
			/* little endian */
			// data_len = recvbuff[4] + (recvbuff[5]<<8);
			/* big endian */
			data_len = recvbuff[5] + (recvbuff[4]<<8);
			PLOG(DBG,"recv cmd = 0x%02x, data len = %d\n",*cmd,data_len);
			if(data_len > 0) // have params
				ret = parse_up_package(*cmd,&content->up_content,recvbuff+6,data_len);
		}
	}
	else
	{
		PLOG(ERR,"role %d or cmd 0x%x is unsupported!\n",role,*cmd);
		ret = VPI_EFAIL;
	}

	return ret;
}

